-
Notifications
You must be signed in to change notification settings - Fork 194
Add match_indices field to Suggestion #798
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
I think @maxomatic458 would be the ideal reviewer here, based on recent work in the completion logic and the discussion we had around the pitfalls with this highlighting. |
|
looks good so far. I think instead of defaulting to this it might be better to just ignore the styling (so basically fully leave it up to the completer). Then we could get rid of this trait function here (since we have the If you are ok with that i can make a PR for that. |
|
@maxomatic458 Interesting idea, I was going to say highlighting shouldn't be the completer's responsibility, but if it simplifies things, that'd be great. It's definitely worth a PR. @sholderbach Thoughts? |
How would that interact with other highlighting functionality in reedline? Eg. it takes care of highlighting the selected item, if the completer dealt with highlighting individual characters how would that mix?
Do you mean decoupling value from how it's displaying, eg. adding something like Slightly tangential, but there's plenty of places that initialize |
I think this might actually be better solution. So instead of adding more stuff to the Suggestion we just have one that can be modified by a seperate styling layer (that can be attached to a completer). But this might require a good amount of breaking changes, so maybe thats something for a future PR? |
Ah, you're right @qfel, highlighting in the completer wouldn't work.
@maxomatic458 As @qfel pointed out, we'd need two display values, one in case the suggestion is selected and one in case it isn't, right? That feels like too much to me.
@maxomatic458 This sounds interesting, could you explain more? (having a hard time figuring out what a separate styling layer would look like) |
|
maybe this could be implemented as a trait pub trait SuggestionStyler {
fn style_selected(&self, suggestion: Suggestion, ...) -> String {
...
}
fn style_unselected(&self, suggestion: Suggestion, ...) -> String {
...
}
}then we could pass a SuggestionStyler struct down to the menu and inside the the functions of the so we would move the styling out of the menu and out of the completer and we can implement it using this trait Edit: |
|
@maxomatic458 Sorry, still not getting it. Do you mean that the |
|
@ysthakur that would potentially allow for more complex styling, without having to make any changes to the menu so something like this here enum MatchMode {
Fuzzy,
Prefix,
}
struct UnderlineStyler {
color: ...,
mode: MatchMode
}
impl SuggestionStyler for UnderlineStyler {
fn style_selected(&mut self, suggestion: Suggestion, cursor_pos: Option<(u16, u16)>, text_typed_by_user: String, terminal_dims: Option<(u16, u16)>, ...) -> Suggestion {
// Do the styling here
...
return suggestion
}
fn style_unselected(...) -> Suggestion {
...
}
}then we could add this and then call the trait functions in menu when we are generating the strings for the suggestions Thats just an idea how it could be implemented if we want to make the suggestion-styling that extensible. Im also ok with just adding the |
|
IIUC that would either mean we keep Or it would mean inside the styler we have to recompute the matching indices, which sounds a bit unpleasant. Unless we allow to attach arbitrary data with suggestions - then completers could generate suggestions with completer-specific hints, and provide stylers that consume those hints to producer stylized output. Though that feels like a fairly big change? |
That was my idea (if you are concerned about the recomputing, i believe in both cases we would need to recompute the indices whenever the user types something). We would attach a Styler to a completer, and that would handle the styling of the suggestions. so the Suggestions struct would not have to be modified again, when we want to introduce a new way of styling suggestions. |
@maxomatic458 Oh, I see now. I like the idea of avoiding adding fields to That said, I agree with you that performance might not be a huge problem. Only a few suggestions are displayed on the screen at any point, even if there are hundreds of suggestions in total. If we only recompute the match indices for each suggestion when it shows up on the screen, the user may not see a noticeable delay. I'm not an expert here, though. Anyone have any idea what impact this would have on performance?
@maxomatic458 Did you have any possible future ways of styling suggestions in mind? Do you mean we might want to highlight suggestions in three different colors (rather than the current two)? |
That aside, potentially having to run the same matching code in two different places in the Completer is an unpleasant API. Not a huge issue I suppose, but makes it a little harder to get things going (and might have some weird implications for non-deterministic/pure matching, maybe if somebody decided to do some server-side completions).
I noticed one of the examples above had this - what would it be used for? Hm let me expand a bit on the styler idea that I think could work well (if we really want to go there):
pub struct Suggestion<T> {
/// String replacement that will be introduced to the the buffer
pub value: String,
/// Replacement span in the buffer
pub span: Span,
/// Whether to append a space after selecting this suggestion.
/// This helps to avoid that a completer repeats the complete suggestion.
pub append_whitespace: bool,
/// Completer-associated context, passed back to Styler.
pub context: T,
}
pub struct DisplaySuggestion {
/// The value to display, often the same as coming from Suggestion but will have ANSI escapes.
pub value: String,
/// Optional description for the replacement, can contain ANSI escapes.
pub description: Option<String>,
/// Optional vector of strings in the suggestion. These can be used to
/// represent examples coming from a suggestion. Can contain ANSI escapes.
pub extra: Option<Vec<String>>,
}
// Probably needs more thought about the naming.
pub trait Styler<T> {
fn display(&self, suggestion: Suggestion<T>, is_selected: bool, some_type_of_user_config: _, ...) -> DisplaySuggestion;
// Also batch_display with some default implementation. May be useful for some completers to parallelize.
}
pub trait Completer {
type Context;
fn complete(&mut self, line: &str, pos: usize) -> Vec<Suggestion<Self::Context>>;
// We *probably* want to instead have it be a separate field on ReedlineMenu::WithCompleter,
// so that any compatible Styler can be used. But that's maybe something to think on later.
//
// This could return one of predefined simple static stylers, or something custom for the completer.
fn styler<'a>(&'a self) -> &'a dyn Styler<Self::Context>;
}
Regarding the |
Yea not really sure what that could be used for either, just came into my mind when thinking of things that might be useful to style a suggestion. I think it would also be useful to store the string the completer used to suggest that suggestion in the Suggestion struct. |
# Description Take advantage of the `Default` implementation of `Suggestion`. This in particular should make code compatible forward-compatible with nushell/reedline#798. # User-Facing Changes None # Tests + Formatting Existing coverage.
@maxomatic458 Would this be used by the
That's a good point, @qfel, running matching twice wouldn't be good for non-deterministic/time-consuming suggestions. That said, @maxomatic458's |
@ysthakur that could be also be used outside of styling. Currently we have the helper function "complete_with_base_ranges", that basically does the same thing already, but instead of collecting the actual strings, we just get the ranges in the buffer (i think). So an extra field that holds the "completion base value" could be used to store that instead. Lines 80 to 87 in ad56a23
|
|
Hi, Thanks. |
|
updating create_string method is enough i think. like this let suggestion_style_prefix = suggestion.clone()
.style
.unwrap_or(Style::new().bold().fg(Color::Cyan))
.prefix();
let extras = suggestion.extra.as_ref().unwrap();
let match_str = extras.get(0).unwrap();
let splits = Self::split_string_by_pattern(&suggestion.value, &match_str).unwrap();
format!(
"{}{}{}{}{}{}{}{}{}{:>empty$}{}{}",
suggestion_style_prefix,
splits.0,
Style::default().underline().prefix(),
match_str,
RESET,
suggestion_style_prefix,
splits.2,
RESET,
Color::Yellow.normal().prefix(),
"",
RESET,
"\r\n",
empty = empty_space,
)for example. |
|
@ysthakur The |
|
This PR now does the ANSI sequence parsing itself, rather than using regex. |
# Description In preparation for nushell/reedline#798, which adds a new field to `Suggestion`, this PR makes sure that `Suggestion`s are created using `..Default::default()` inside `attribute_completions.rs`. # User-Facing Changes None # Tests + Formatting None # After Submitting
Make columnar_menu use match indices Make ide menu use match indices Add fuzzy completions example Test style_suggestion Make doctests in default.rs pass Highlight entire graphemes Extract ANSI escapes from strings to apply match highlighting Fix clippy lint for fuzzy completion example Shut the typo checker up Use existing variable `escape` Copy regex from parse-ansi crate
Fix padding for columnar menu Highlight substring matches too by default Simplify (?) columnar menu
c2dc5fc to
e18f52a
Compare
|
I think this is ready to be merged! @maxomatic458 could you look over it to make sure it looks alright? |
kubouch
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have anything against merging it
Use the latest commit of Reedline on the `main` branch so we can use [#798](nushell/reedline#798) (Add match_indices field to Suggestion) <!-- Thank you for improving Nushell! Please, read our contributing guide: https://github.com/nushell/nushell/blob/main/CONTRIBUTING.md --> ## Release notes summary - What our users need to know <!-- This section will be included as part of our release notes. See the contributing guide for more details. Please include only details relevant for users here. Motivation and technical details can be added above or below this section. You may leave this section blank until your PR is finalized. Ask a core team member if you need help filling this section. --> N/A ## Tasks after submitting <!-- Remove any tasks which aren't relevant for your PR, or add your own --> - [ ] Make use of the new Suggestion field in #15887
* fix: prompt glitch when resizing with cursor in a multiline command (nushell#898) * fix: prompt glitch when resizing with cursor in a multiline command * fix: considering line wraps * fix: clippy * Bump version to `0.41.0` (nushell#936) * Protect against invalid suggestion spans (nushell#915) * Protect against invalid suggestion spans * Protect against start > end * feat: Builder option to immediately accept input (nushell#933) * feat: Option to immediately accept input * chore: PR feedback * feat: add `ViChangeMode` event (nushell#932) * feat: add `ViChangeMode` event * fix: use `FromStr` trait * fix: undo unused change * fix: clippy * Fix `mismatched_lifetime_syntaxes` (nushell#947) From Rust 1.89.0 on this lints. https://blog.rust-lang.org/2025/08/07/Rust-1.89.0/#mismatched-lifetime-syntaxes-lint * Fix missing import in README.md (nushell#942) Co-authored-by: sholderbach <[email protected]> * fix: dislocation of cursor after previous_history navigation to a multiline entry (nushell#899) * bumping version to 42 (nushell#949) Co-authored-by: Jack Wright <[email protected]> * Bump `rusqlite` to 0.37 (nushell#950) * feat: make columnar menu traversal direction configurable (nushell#951) * Make columnar menu traversal direction configurable * Add tests for columnar menu selection position updates * Apply changes based on review * Upgrade GitHub Actions and Rust toolchain versions (nushell#954) * Upgrade GitHub Actions and Rust toolchain versions * Update .github/workflows/ci.yml * Vi mode text objects for word, WORD, brackets and quotes (nushell#939) * Addd initial change inner and around word text objects and handle whitespace Note a buffer full of whitespace is not properly considered and still causing incorrect behaviour. TODO fix this. * Renaming functions and fix default value of current_whitespace_range_start * Rename cut/yank inside enums and methods to cut/yank inside pair and add general yank/cut range methods * Use TextObject enum instead of passing through the character * WIP: Add quote and bracket text objects and add jumping if not inside objects * Add my own methods for finding matching pair and jumping * Fix bugs in new function to get matching pair range and it's finish features - Now handles jumps to next open/close or equal symbol pairs if not in a pair already - Searching only on current line for equal symbol pairs e.g. quotes - Correctly handles graphemes * Simplify heirarchy of pair range finding functions - Refactor the structure of the methods to get ranges, don't need to pass in depth unecessarily, high level functions don't require cursor passed in. - Now two seperate functions for ranges, one "next" and one "current" range that gets you either the range inside next text object or inside current one depending on position of cursor. - Finilise logic to correctly handle graphemes (not byte sized chars) TODO Update unit tests * Refactoring range functions and tidy up/extend unit test cases and coverage * More refactoring - Improve some text object ranges to use iterators rather than complex logic - Clean up documentation, add consts etc - Look through and refactor some editor functions * Move text object range methods into line_buffer from editor * Combine line_buffer quote and pair text object functions into generic and rewrite a lot of doc strings * Testing for quote and bracket text object functions in editor.rs * Whitespace * Rework unit tests for new function structure * Remove angle brackets from b text object * Rename yank text object functions to copy * Add bracket test cases to range_inside_next_pair_in_group unit tests * Add more detailed unicode safety tests * Fix display enum string for renamed enums * Unicode and overflow/underflow safety when expanding text object ranges * Pass through matching pair group const for quote and bracket text object functions * Rename yank_range -> copy_range for consistency with other methods * Remove unecessary guard clause from expand_range_to_include_pair * Correct display string for CutInsidePair * Make text object types public (nushell#957) * Make TextObject, TextObjectScope and TextObjectType public * Correct CopyInsidePair and CopyAroundPair EditType to NoOp * fix: dislocation of cursor during history navigation (nushell#959) * fix: dislocation of cursor during history navigation * test: new test case by Claude Sonnet * simplify * Fix typos (nushell#962) * Bump version for `0.43.0` release (nushell#961) * Fix shift selection in vi (insert) & emacs mode (nushell#927) * Add match_indices field to Suggestion (nushell#798) * Add match_indices field to Suggestion Make columnar_menu use match indices Make ide menu use match indices Add fuzzy completions example Test style_suggestion Make doctests in default.rs pass Highlight entire graphemes Extract ANSI escapes from strings to apply match highlighting Fix clippy lint for fuzzy completion example Shut the typo checker up Use existing variable `escape` Copy regex from parse-ansi crate * replace LazyLock with lazy_static that works with Rust 1.63.0 (#2) * Homegrown ANSI parser Fix padding for columnar menu Highlight substring matches too by default Simplify (?) columnar menu * Fix clippy lints after rebase * Use get_match_indices helper * Stop using 'fo' because it's a typo? Fo shizzle. * Use to_string() instead of as_str() * Style entire suggestion same color * RESET after suggestion --------- Co-authored-by: Divanshu Grover <[email protected]> * fix bashism parsing (nushell#958) * refactor: use crossterm `supports_keyboard_enhancement` once when KittyProtocolGuard is initialized (nushell#920) Co-authored-by: Pierre POLLET <[email protected]> --------- Co-authored-by: zc he <[email protected]> Co-authored-by: Stefan Holderbach <[email protected]> Co-authored-by: Yash Thakur <[email protected]> Co-authored-by: Stuart Carnie <[email protected]> Co-authored-by: Daniel Bonofiglio <[email protected]> Co-authored-by: Daniel del Castillo <[email protected]> Co-authored-by: Jack Wright <[email protected]> Co-authored-by: Jack Wright <[email protected]> Co-authored-by: Piepmatz <[email protected]> Co-authored-by: simonborje <[email protected]> Co-authored-by: Darren Schroeder <[email protected]> Co-authored-by: JonLD <[email protected]> Co-authored-by: Collin Murch <[email protected]> Co-authored-by: Divanshu Grover <[email protected]> Co-authored-by: migraine-user <[email protected]> Co-authored-by: PtiBouchon <[email protected]> Co-authored-by: Pierre POLLET <[email protected]>
# Description Requires nushell/reedline#798, which adds a `match_indices` field to `Suggestion` that allows completers to specify exactly which graphemes in the suggestion matched the typed text. This PR takes advantage of this field to accurately underline fuzzily-matched suggestions. <img width="890" height="311" alt="image" src="https://github.com/user-attachments/assets/74443a82-1fe8-4a92-8679-41e28b871017" /> Note that file/directory/dotnu completions don't currently make use of the `match_indices` field. Once we have a way to have separate display values, this will be much easier to implement. # User-Facing Changes Users will see sensible highlighting for suggestions when using fuzzy matching, nothing else. # Tests + Formatting Just manually triggered completions to make sure things look okay. # After Submitting No need to update docs for this. --------- Co-authored-by: blindfs <[email protected]>

Description
This PR adds a
match_indices: Option<Vec<usize>>field to theSuggestionstruct representing which indices of the Suggestion'svaluefield were matched.Motivation
Currently, in Nushell, if you have fuzzy matching on, the matches are still highlighted with the assumption that you're using prefix matching:

This PR doesn't fix that, but adding
match_indicestoSuggestiondoes make it so that completers using fuzzy matching can include the indices of the matches in their returned suggestions. nushell/nushell#15887 will actually fix the issue in Nushell once this PR is merged. Here's thefuzzy_completionsexample included in this PR, which uses a columnar menu (ignore how bad the fuzzy completion algorithm is, I didn't bother using skim or Nucleo):And modified to use an IDE menu with fake descriptions:
Tests
I only added a test for the
style_suggestionhelper that I added for highlighting the matched portions of a suggestion. Other than that, I just manually tried the menus out a bit. If there are any edge cases anyone can think of to test (either through unit tests or manually), I'd appreciate that.I have a example for a columnar menu with fuzzy completions that you can copy into the
examples/folder and run for testing: https://gist.github.com/ysthakur/9cbb1fc1eec678eb2445ed7dff71fcdd.After submitting
I plan to make a few more PRs:
match_indicesfield (Set match_indices for fuzzy-matched suggestions nushell#15887)display_value: Option<String>field toSuggestion(suggested by qfel here), giving Reedline consumers more control over what their users seedisplay_valueandmatch_indices